/*******************************************************************************
# Linux-UVC streaming input-plugin for MJPG-streamer                           #
#                                                                              #
# This package work with the Logitech UVC based webcams with the mjpeg feature #
#                                                                              #
# Copyright (C) 2005 2006 Laurent Pinchart &&  Michel Xhaard                   #
#                    2007 Lucas van Staden                                     #
#                    2007 Tom Stöveken                                         #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation; either version 2 of the License, or            #
# (at your option) any later version.                                          #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <syslog.h>

#include <linux/types.h>          /* for videodev2.h */
#include <linux/videodev2.h>

#include "../../utils.h"
#include "v4l2uvc.h" // this header will includes the ../../mjpg_streamer.h

#ifndef NO_LIBJPEG
    #include "jpeg_utils.h"
    #include "huffman.h"
#endif

#include "dynctrl.h"


#define INPUT_PLUGIN_NAME "UVC webcam grabber"

/*
 * UVC resolutions mentioned at: (at least for some webcams)
 * http://www.quickcamteam.net/hcl/frame-format-matrix/
 */
static const struct {
    const char *string;
    const int width, height;
} resolutions[] = {
    { "QSIF", 160,  120  },
    { "QCIF", 176,  144  },
    { "CGA",  320,  200  },
    { "QVGA", 320,  240  },
    { "CIF",  352,  288  },
    { "VGA",  640,  480  },
    { "SVGA", 800,  600  },
    { "XGA",  1024, 768  },
    { "SXGA", 1280, 1024 }
};

static const struct {
    const char *string;
    const v4l2_std_id vstd;
} norms[] = {
    { "UNKNOWN", V4L2_STD_UNKNOWN },
    { "PAL", V4L2_STD_PAL },
    { "NTSC", V4L2_STD_NTSC },
    { "SECAM", V4L2_STD_SECAM }
};

/* private functions and variables to this plugin */
static globals *pglobal;
static int gquality = 80;
static unsigned int minimum_size = 0;
static int dynctrls = 1;

void *cam_thread(void *);
void cam_cleanup(void *);
void help(void);
int input_cmd(int plugin, unsigned int control, unsigned int group, int value, char *value_string);
int uvcPanTilt(int dev, int pan, int tilt, int reset);

const char *get_name_by_tvnorm(v4l2_std_id vstd) {
	int i;
	for (i=0;i<sizeof(norms);i++) {
		if (vstd == norms[i].vstd) {
			return norms[i].string;
		}
	}
	return norms[0].string;
}

/*** plugin interface functions ***/
/******************************************************************************
Description.: This function ializes the plugin. It parses the commandline-
              parameter and stores the default and parsed values in the
              appropriate variables.
Input Value.: param contains among others the command-line string
Return Value: 0 if everything is fine
              1 if "--help" was triggered, in this case the calling programm
              should stop running and leave.
******************************************************************************/
int input_init(input_parameter *param, int id)
{
    char *dev = "/dev/video0", *s;
    int width = 640, height = 480, fps = -1, format = V4L2_PIX_FMT_MJPEG, i;
    v4l2_std_id tvnorm = V4L2_STD_UNKNOWN;

    /* initialize the mutes variable */
    if(pthread_mutex_init(&cams[id].controls_mutex, NULL) != 0) {
        IPRINT("could not initialize mutex variable\n");
        exit(EXIT_FAILURE);
    }

    param->argv[0] = INPUT_PLUGIN_NAME;
    param->global->in[id].name = malloc((strlen(INPUT_PLUGIN_NAME) + 1) * sizeof(char));
    sprintf(param->global->in[id].name, INPUT_PLUGIN_NAME);

    /* show all parameters for DBG purposes */
    for(i = 0; i < param->argc; i++) {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    /* parse the parameters */
    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0
            },
            {"help", no_argument, 0, 0},
            {"d", required_argument, 0, 0},
            {"device", required_argument, 0, 0},
            {"r", required_argument, 0, 0},
            {"resolution", required_argument, 0, 0},
            {"f", required_argument, 0, 0},
            {"fps", required_argument, 0, 0},
            {"y", no_argument, 0, 0},
            {"yuv", no_argument, 0, 0},
            {"q", required_argument, 0, 0},
            {"quality", required_argument, 0, 0},
            {"m", required_argument, 0, 0},
            {"minimum_size", required_argument, 0, 0},
            {"n", no_argument, 0, 0},
            {"no_dynctrl", no_argument, 0, 0},
            {"l", required_argument, 0, 0},
            {"led", required_argument, 0, 0},
            {"fourcc", required_argument, 0, 0},
            {"t", required_argument, 0, 0 },
	        {"tvnorm", required_argument, 0, 0 },
            {0, 0, 0, 0}
        };

        /* parsing all parameters according to the list above is sufficent */
        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            return 1;
        }

        /* dispatch the given options */
        switch(option_index) {
        /* h, help */
        case 0:
        case 1:
            DBG("case 0,1\n");
            help();
            return 1;
            break;

        /* d, device */
        case 2:
        case 3:
            DBG("case 2,3\n");
            dev = strdup(optarg);
            break;

        /* r, resolution */
        case 4:
        case 5:
            DBG("case 4,5\n");
            width = -1;
            height = -1;

            /* try to find the resolution in lookup table "resolutions" */
            for(i = 0; i < LENGTH_OF(resolutions); i++) {
                if(strcmp(resolutions[i].string, optarg) == 0) {
                    width  = resolutions[i].width;
                    height = resolutions[i].height;
                }
            }
            /* done if width and height were set */
            if(width != -1 && height != -1)
                break;
            /* parse value as decimal value */
            width  = strtol(optarg, &s, 10);
            height = strtol(s + 1, NULL, 10);
            break;

        /* f, fps */
        case 6:
        case 7:
            DBG("case 6,7\n");
            fps = atoi(optarg);
            break;

        /* y, yuv */
        #ifndef NO_LIBJPEG
        case 8:
        case 9:
            DBG("case 8,9\n");
            format = V4L2_PIX_FMT_YUYV;
            break;
        #endif
        /* q, quality */
        #ifndef NO_LIBJPEG
        case 10:
        case 11:
            DBG("case 10,11\n");
            gquality = MIN(MAX(atoi(optarg), 0), 100);
            break;
        #endif
        /* m, minimum_size */
        case 12:
        case 13:
            DBG("case 12,13\n");
            minimum_size = MAX(atoi(optarg), 0);
            break;

        /* n, no_dynctrl */
        case 14:
        case 15:
            DBG("case 14,15\n");
            dynctrls = 0;
            break;

            /* l, led */
        case 16:
        case 17:/*
        DBG("case 16,17\n");
        if ( strcmp("on", optarg) == 0 ) {
          led = IN_CMD_LED_ON;
        } else if ( strcmp("off", optarg) == 0 ) {
          led = IN_CMD_LED_OFF;
        } else if ( strcmp("auto", optarg) == 0 ) {
          led = IN_CMD_LED_AUTO;
        } else if ( strcmp("blink", optarg) == 0 ) {
          led = IN_CMD_LED_BLINK;
        }*/
            break;
        /* fourcc */
        #ifndef NO_LIBJPEG
        case 18:
            DBG("case 18,19\n");
            if (strcmp(optarg, "RGBP") == 0) {
                format = V4L2_PIX_FMT_RGB565;
            } else if (strcmp(optarg, "RGB3") == 0) {
                format = V4L2_PIX_FMT_RGB24;
            } else {
                DBG("FOURCC %s not supported\n", optarg);
            }
            break;
        #endif
        /* t, tvnorm */
        case 19:
        case 20:
            DBG("case 19,20\n");
            if (strcasecmp("pal",optarg) == 0 ) {
	             tvnorm = V4L2_STD_PAL;
            } else if ( strcasecmp("ntsc",optarg) == 0 ) {
	             tvnorm = V4L2_STD_NTSC;
            } else if ( strcasecmp("secam",optarg) == 0 ) {
	             tvnorm = V4L2_STD_SECAM;
            }
            break;
        default:
            DBG("default case\n");
            help();
            return 1;
        }
    }
    DBG("input id: %d\n", id);
    cams[id].id = id;
    cams[id].pglobal = param->global;

    /* allocate webcam datastructure */
    cams[id].videoIn = malloc(sizeof(struct vdIn));
    if(cams[id].videoIn == NULL) {
        IPRINT("not enough memory for videoIn\n");
        exit(EXIT_FAILURE);
    }
    memset(cams[id].videoIn, 0, sizeof(struct vdIn));

    /* display the parsed values */
    IPRINT("Using V4L2 device.: %s\n", dev);
    IPRINT("Desired Resolution: %i x %i\n", width, height);
    if (fps == -1) {
        IPRINT("Frames Per Second.: not limited\n");
    } else {
        IPRINT("Frames Per Second.: %i\n", fps);
    }

    char *fmtString = NULL;
    switch (format) {
        case V4L2_PIX_FMT_MJPEG:
            fmtString = "JPEG";
            break;
        #ifndef NO_LIBJPG
            case V4L2_PIX_FMT_YUYV:
                fmtString = "YUYV";
                break;
            case V4L2_PIX_FMT_RGB565:
                fmtString = "RGB565";
                break;
            case V4L2_PIX_FMT_RGB24:
                fmtString = "RGB3";
                break;
        #endif
        default:
            fmtString = "Unknown format";
    }

    IPRINT("Format............: %s\n", fmtString);
    #ifndef NO_LIBJPEG
        if(format != V4L2_PIX_FMT_MJPEG)
            IPRINT("JPEG Quality......: %d\n", gquality);
    #endif

    if (tvnorm != V4L2_STD_UNKNOWN) {
        IPRINT("TV-Norm...........: %s\n", get_name_by_tvnorm(tvnorm));
    } else {
        IPRINT("TV-Norm...........: DEFAULT\n");
    }

    DBG("vdIn pn: %d\n", id);
    /* open video device and prepare data structure */
    if(init_videoIn(cams[id].videoIn, dev, width, height, fps, format, 1, cams[id].pglobal, id, tvnorm) < 0) {
        IPRINT("init_VideoIn failed\n");
        closelog();
        exit(EXIT_FAILURE);
    }
    /*
     * recent linux-uvc driver (revision > ~#125) requires to use dynctrls
     * for pan/tilt/focus/...
     * dynctrls must get initialized
     */
    if(dynctrls)
        initDynCtrls(cams[id].videoIn->fd);

    enumerateControls(cams[id].videoIn, cams[id].pglobal, id); // enumerate V4L2 controls after UVC extended mapping

    return 0;
}

/******************************************************************************
Description.: Stops the execution of worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int input_stop(int id)
{
    DBG("will cancel camera thread #%02d\n", id);
    pthread_cancel(cams[id].threadID);
    return 0;
}

/******************************************************************************
Description.: spins of a worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int input_run(int id)
{
    cams[id].pglobal->in[id].buf = malloc(cams[id].videoIn->framesizeIn);
    if(cams[id].pglobal->in[id].buf == NULL) {
        fprintf(stderr, "could not allocate memory\n");
        exit(EXIT_FAILURE);
    }

    DBG("launching camera thread #%02d\n", id);
    /* create thread and pass context to thread function */
    pthread_create(&(cams[id].threadID), NULL, cam_thread, &(cams[id]));
    pthread_detach(cams[id].threadID);
    return 0;
}

/*** private functions for this plugin below ***/
/******************************************************************************
Description.: print a help message to stderr
Input Value.: -
Return Value: -
******************************************************************************/
void help(void)
{
    int i;

    fprintf(stderr, " ---------------------------------------------------------------\n" \
    " Help for input plugin..: "INPUT_PLUGIN_NAME"\n" \
    " ---------------------------------------------------------------\n" \
    " The following parameters can be passed to this plugin:\n\n" \
    " [-d | --device ].......: video device to open (your camera)\n" \
    " [-r | --resolution ]...: the resolution of the video device,\n" \
    "                          can be one of the following strings:\n" \
    "                          ");

    for(i = 0; i < LENGTH_OF(resolutions); i++) {
        fprintf(stderr, "%s ", resolutions[i].string);
        if((i + 1) % 6 == 0)
            fprintf(stderr, "\n                          ");
    }
    fprintf(stderr, "\n                          or a custom value like the following" \
    "\n                          example: 640x480\n");

#ifndef NO_LIBJPEG
    fprintf(stderr, " [-f | --fps ]..........: frames per second\n" \
    "                          (activates YUYV format, disables MJPEG)\n" \
    " [-m | --minimum_size ].: drop frames smaller then this limit, useful\n" \
    "                          if the webcam produces small-sized garbage frames\n" \
    "                          may happen under low light conditions\n" \
    " [-n | --no_dynctrl ]...: do not initalize dynctrls of Linux-UVC driver\n" \
    " [-l | --led ]..........: switch the LED \"on\", \"off\", let it \"blink\" or leave\n" \
    "                          it up to the driver using the value \"auto\"\n" \
    " ---------------------------------------------------------------\n\n"
    " [-t | --tvnorm ] ......: set TV-Norm pal, ntsc or secam\n"
    " ---------------------------------------------------------------\n\n");
#else
    fprintf(stderr, " [-f | --fps ]..........: frames per second\n" \
    "                          (activates YUYV format, disables MJPEG)\n" \
    " [-m | --minimum_size ].: drop frames smaller then this limit, useful\n" \
    "                          if the webcam produces small-sized garbage frames\n" \
    "                          may happen under low light conditions\n" \
    " [-n | --no_dynctrl ]...: do not initalize dynctrls of Linux-UVC driver\n" \
    " [-l | --led ]..........: switch the LED \"on\", \"off\", let it \"blink\" or leave\n" \
    "                          it up to the driver using the value \"auto\"\n" \
    " [-t | --tvnorm ] ......: set TV-Norm pal, ntsc or secam\n"
    " ---------------------------------------------------------------\n\n");
#endif
}

/******************************************************************************
Description.: this thread worker grabs a frame and copies it to the global buffer
Input Value.: unused
Return Value: unused, always NULL
******************************************************************************/
void *cam_thread(void *arg)
{

    context *pcontext = arg;
    pglobal = pcontext->pglobal;

    /* set cleanup handler to cleanup allocated ressources */
    pthread_cleanup_push(cam_cleanup, pcontext);

    while(!pglobal->stop) {
        while(pcontext->videoIn->streamingState == STREAMING_PAUSED) {
            usleep(1); // maybe not the best way so FIXME
        }

        /* grab a frame */
        if(uvcGrab(pcontext->videoIn) < 0) {
            IPRINT("Error grabbing frames\n");
            exit(EXIT_FAILURE);
        }

        //DBG("received frame of size: %d from plugin: %d\n", pcontext->videoIn->buf.bytesused, pcontext->id);

        /*
         * Workaround for broken, corrupted frames:
         * Under low light conditions corrupted frames may get captured.
         * The good thing is such frames are quite small compared to the regular pictures.
         * For example a VGA (640x480) webcam picture is normally >= 8kByte large,
         * corrupted frames are smaller.
         */
        if(pcontext->videoIn->buf.bytesused < minimum_size) {
            DBG("dropping too small frame, assuming it as broken\n");
            continue;
        }

        // use software frame dropping on low fps
        if (pcontext->videoIn->soft_framedrop == 1) {
            unsigned long last = pglobal->in[pcontext->id].timestamp.tv_sec * 1000 +
                                (pglobal->in[pcontext->id].timestamp.tv_usec/1000); // convert to ms
            unsigned long current = pcontext->videoIn->buf.timestamp.tv_sec * 1000 +
                                    pcontext->videoIn->buf.timestamp.tv_usec/1000; // convert to ms

            // if the requested time did not esplashed skip the frame
            if ((current - last) < pcontext->videoIn->frame_period_time) {
                //DBG("Last frame taken %d ms ago so drop it\n", (current - last));
                continue;
            }
            DBG("Lagg: %ld\n", (current - last) - pcontext->videoIn->frame_period_time);
        }

        /* copy JPG picture to global buffer */
        pthread_mutex_lock(&pglobal->in[pcontext->id].db);

        /*
         * If capturing in YUV mode convert to JPEG now.
         * This compression requires many CPU cycles, so try to avoid YUV format.
         * Getting JPEGs straight from the webcam, is one of the major advantages of
         * Linux-UVC compatible devices.
         */
        #ifndef NO_LIBJPEG
        if ((pcontext->videoIn->formatIn == V4L2_PIX_FMT_YUYV) ||
            (pcontext->videoIn->formatIn == V4L2_PIX_FMT_RGB565) ||
            (pcontext->videoIn->formatIn == V4L2_PIX_FMT_RGB24)) {
            DBG("compressing frame from input: %d\n", (int)pcontext->id);
            pglobal->in[pcontext->id].size = compress_image_to_jpeg(pcontext->videoIn, pglobal->in[pcontext->id].buf, pcontext->videoIn->framesizeIn, gquality);
        } else {
        #endif
            //DBG("copying frame from input: %d\n", (int)pcontext->id);
            pglobal->in[pcontext->id].size = memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->buf.bytesused);
        #ifndef NO_LIBJPEG
        }
        #endif

#if 0
        /* motion detection can be done just by comparing the picture size, but it is not very accurate!! */
        if((prev_size - global->size)*(prev_size - global->size) > 4 * 1024 * 1024) {
            DBG("motion detected (delta: %d kB)\n", (prev_size - global->size) / 1024);
        }
        prev_size = global->size;
#endif

        /* copy this frame's timestamp to user space */
        pglobal->in[pcontext->id].timestamp = pcontext->videoIn->buf.timestamp;

        /* signal fresh_frame */
        pthread_cond_broadcast(&pglobal->in[pcontext->id].db_update);
        pthread_mutex_unlock(&pglobal->in[pcontext->id].db);
    }

    DBG("leaving input thread, calling cleanup function now\n");
    pthread_cleanup_pop(1);

    return NULL;
}

/******************************************************************************
Description.:
Input Value.:
Return Value:
******************************************************************************/
void cam_cleanup(void *arg)
{
    static unsigned char first_run = 1;
    context *pcontext = arg;
    pglobal = pcontext->pglobal;
    if(!first_run) {
        DBG("already cleaned up ressources\n");
        return;
    }

    first_run = 0;
    IPRINT("cleaning up ressources allocated by input thread\n");

    close_v4l2(pcontext->videoIn);
    if(pcontext->videoIn->tmpbuffer != NULL) free(pcontext->videoIn->tmpbuffer);
    if(pcontext->videoIn != NULL) free(pcontext->videoIn);
    if(pglobal->in[pcontext->id].buf != NULL)
        free(pglobal->in[pcontext->id].buf);
}

/******************************************************************************
Description.: process commands, allows to set v4l2 controls
Input Value.: * control specifies the selected v4l2 control's id
                see struct v4l2_queryctr in the videodev2.h
              * value is used for control that make use of a parameter.
Return Value: depends in the command, for most cases 0 means no errors and
              -1 signals an error. This is just rule of thumb, not more!
******************************************************************************/
int input_cmd(int plugin_number, unsigned int control_id, unsigned int group, int value, char *value_string)
{
    int ret = -1;
    int i = 0;
    DBG("Requested cmd (id: %d) for the %d plugin. Group: %d value: %d\n", control_id, plugin_number, group, value);
    switch(group) {
    case IN_CMD_GENERIC: {
            int i;
            for (i = 0; i<pglobal->in[plugin_number].parametercount; i++) {
                if ((pglobal->in[plugin_number].in_parameters[i].ctrl.id == control_id) &&
                    (pglobal->in[plugin_number].in_parameters[i].group == IN_CMD_GENERIC)){
                    DBG("Generic control found (id: %d): %s\n", control_id, pglobal->in[plugin_number].in_parameters[i].ctrl.name);
                    DBG("New %s value: %d\n", pglobal->in[plugin_number].in_parameters[i].ctrl.name, value);
                    return 0;
                }
            }
            DBG("Requested generic control (%d) did not found\n", control_id);
            return -1;
        } break;
    case IN_CMD_V4L2: {
            ret = v4l2SetControl(cams[plugin_number].videoIn, control_id, value, plugin_number, pglobal);
            if(ret == 0) {
                pglobal->in[plugin_number].in_parameters[i].value = value;
            } else {
                DBG("v4l2SetControl failed: %d\n", ret);
            }
            return ret;
        } break;
    case IN_CMD_RESOLUTION: {
        // the value points to the current formats nth resolution
        if(value > (pglobal->in[plugin_number].in_formats[pglobal->in[plugin_number].currentFormat].resolutionCount - 1)) {
            DBG("The value is out of range");
            return -1;
        }
        int height = pglobal->in[plugin_number].in_formats[pglobal->in[plugin_number].currentFormat].supportedResolutions[value].height;
        int width = pglobal->in[plugin_number].in_formats[pglobal->in[plugin_number].currentFormat].supportedResolutions[value].width;
        ret = setResolution(cams[plugin_number].videoIn, width, height);
        if(ret == 0) {
            pglobal->in[plugin_number].in_formats[pglobal->in[plugin_number].currentFormat].currentResolution = value;
        }
        return ret;
    } break;
    case IN_CMD_JPEG_QUALITY:
        if((value >= 0) && (value < 101)) {
            pglobal->in[plugin_number].jpegcomp.quality = value;
            if(IOCTL_VIDEO(cams[plugin_number].videoIn->fd, VIDIOC_S_JPEGCOMP, &pglobal->in[plugin_number].jpegcomp) != EINVAL) {
                DBG("JPEG quality is set to %d\n", value);
                ret = 0;
            } else {
                DBG("Setting the JPEG quality is not supported\n");
            }
        } else {
            DBG("Quality is out of range\n");
        }
        break;
    }
    return ret;
}

/******************************************************************************
Description.: This method provides backward compatibility for the command
              method used in revs<94.
              Process commands, allows to set certain runtime configurations
              and settings like pan/tilt, colors, saturation etc.
Input Value.: * cmd specifies the command, a complete list is maintained in
                the file "input.h"
              * value is used for commands that make use of a parameter.
Return Value: depends in the command, for most cases 0 means no errors and
              -1 signals an error. This is just rule of thumb, not more!
******************************************************************************/
int input_cmd_old(in_cmd_type cmd, int id, int value) {
  int res=0;
  static int pan=0, tilt=0, pan_tilt_valid=-1;
  static int focus=-1;
  const int one_degree = ONE_DEGREE;

  /* certain commands do not need the mutex */
  if ( cmd != IN_CMD_RESET_PAN_TILT_NO_MUTEX )
    pthread_mutex_lock( &cams[id].controls_mutex);

  switch (cmd) {
    case IN_CMD_HELLO:
      fprintf(stderr, "Hello from input plugin\n");
      break;

    case IN_CMD_RESET:
      DBG("about to reset all image controls to defaults\n");
      res = v4l2ResetControl(cams[id].videoIn, V4L2_CID_BRIGHTNESS);
      res |= v4l2ResetControl(cams[id].videoIn, V4L2_CID_CONTRAST);
      res |= v4l2ResetControl(cams[id].videoIn, V4L2_CID_SATURATION);
      res |= v4l2ResetControl(cams[id].videoIn, V4L2_CID_GAIN);
      if ( res != 0 ) res = -1;
      break;

    case IN_CMD_RESET_PAN_TILT:
    case IN_CMD_RESET_PAN_TILT_NO_MUTEX:
      DBG("about to set pan/tilt to default position\n");
      if ( uvcPanTilt(cams[id].videoIn->fd, 0, 0, 3) != 0 ) {
        res = -1;
        break;
      }
      pan_tilt_valid = 1;
      pan = tilt = 0;
      sleep(4);
      break;

    case IN_CMD_PAN_SET:
      DBG("set pan to %d degrees\n", value);

      /* in order to calculate absolute positions we must check for initialized values */
      if ( pan_tilt_valid != 1 ) {
        if ( input_cmd_old(IN_CMD_RESET_PAN_TILT_NO_MUTEX, id, 0) == -1 ) {
          res = -1;
          break;
        }
      }

      /* limit pan-value to min and max, multiply it with constant "one_degree" */
      value = MIN(MAX(value*one_degree, MIN_PAN), MAX_PAN);

      /* calculate the relative degrees to move to the desired absolute pan-value */
      if( (res = value - pan) == 0 ) {
        /* do not move if this would mean to move by 0 degrees */
        res = pan/one_degree;
        break;
      }

      /* move it */
      pan = value;
      uvcPanTilt(cams[id].videoIn->fd, res, 0, 0);
      res = pan/one_degree;

      DBG("pan: %d\n", pan);
      break;

    case IN_CMD_PAN_PLUS:
      DBG("pan +\n");

      if ( pan_tilt_valid != 1 ) {
        if ( input_cmd_old(IN_CMD_RESET_PAN_TILT_NO_MUTEX, id, 0) == -1 ) {
          res = -1;
          break;
        }
      }

      if ( (MAX_PAN) >= (pan+MIN_RES) ) {
        pan += MIN_RES;
        uvcPanTilt(cams[id].videoIn->fd, MIN_RES, 0, 0);
      }
      res = pan/one_degree;

      DBG("pan: %d\n", pan);
      break;

    case IN_CMD_PAN_MINUS:
      DBG("pan -\n");

      if ( pan_tilt_valid != 1 ) {
        if ( input_cmd_old(IN_CMD_RESET_PAN_TILT_NO_MUTEX, id, 0) == -1 ) {
          res = -1;
          break;
        }
      }

      if ( (MIN_PAN) <= (pan-MIN_RES) ) {
        pan -= MIN_RES;
        uvcPanTilt(cams[id].videoIn->fd, -MIN_RES, 0, 0);
      }
      res = pan/one_degree;

      DBG("pan: %d\n", pan);
      break;

    case IN_CMD_TILT_SET:
      DBG("set tilt to %d degrees\n", value);

      if ( pan_tilt_valid != 1 ) {
        if ( input_cmd_old(IN_CMD_RESET_PAN_TILT_NO_MUTEX, id, 0) == -1 ) {
          res = -1;
          break;
        }
      }

      /* limit pan-value to min and max, multiply it with constant "one_degree" */
      value = MIN(MAX(value*one_degree, MIN_TILT), MAX_TILT);

      /* calculate the relative degrees to move to the desired absolute pan-value */
      if( (res = value - tilt) == 0 ) {
        /* do not move if this would mean to move by 0 degrees */
        res = tilt/one_degree;
        break;
      }

      /* move it */
      tilt = value;
      uvcPanTilt(cams[id].videoIn->fd, 0, res, 0);
      res = tilt/one_degree;

      DBG("tilt: %d\n", tilt);
      break;

    case IN_CMD_TILT_PLUS:
      DBG("tilt +\n");

      if ( pan_tilt_valid != 1 ) {
        if ( input_cmd_old(IN_CMD_RESET_PAN_TILT_NO_MUTEX, id, 0) == -1 ) {
          res = -1;
          break;
        }
      }

      if ( (MAX_TILT) >= (tilt+MIN_RES) ) {
        tilt += MIN_RES;
        uvcPanTilt(cams[id].videoIn->fd, 0, MIN_RES, 0);
      }
      res = tilt/one_degree;

      DBG("tilt: %d\n", tilt);
      break;

    case IN_CMD_TILT_MINUS:
      DBG("tilt -\n");

      if ( pan_tilt_valid != 1 ) {
        if ( input_cmd_old(IN_CMD_RESET_PAN_TILT_NO_MUTEX, id, 0) == -1 ) {
          res = -1;
          break;
        }
      }

      if ( (MIN_TILT) <= (tilt-MIN_RES) ) {
        tilt -= MIN_RES;
        uvcPanTilt(cams[id].videoIn->fd, 0, -MIN_RES, 0);
      }
      res = tilt/one_degree;

      DBG("tilt: %d\n", tilt);
      break;

    case IN_CMD_SATURATION_PLUS:
      DBG("saturation + (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_SATURATION));
      res = v4l2UpControl(cams[id].videoIn, V4L2_CID_SATURATION);
      break;

    case IN_CMD_SATURATION_MINUS:
      DBG("saturation - (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_SATURATION));
      res = v4l2DownControl(cams[id].videoIn, V4L2_CID_SATURATION);
      break;

    case IN_CMD_CONTRAST_PLUS:
      DBG("contrast + (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_CONTRAST));
      res = v4l2UpControl(cams[id].videoIn, V4L2_CID_CONTRAST);
      break;

    case IN_CMD_CONTRAST_MINUS:
      DBG("contrast - (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_CONTRAST));
      res = v4l2DownControl(cams[id].videoIn, V4L2_CID_CONTRAST);
      break;

    case IN_CMD_BRIGHTNESS_PLUS:
      DBG("brightness + (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_BRIGHTNESS));
      res = v4l2UpControl(cams[id].videoIn, V4L2_CID_BRIGHTNESS);
      break;

    case IN_CMD_BRIGHTNESS_MINUS:
      DBG("brightness - (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_BRIGHTNESS));
      res = v4l2DownControl(cams[id].videoIn, V4L2_CID_BRIGHTNESS);
      break;

    case IN_CMD_GAIN_PLUS:
      DBG("gain + (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_GAIN));
      res = v4l2UpControl(cams[id].videoIn, V4L2_CID_GAIN);
      break;

    case IN_CMD_GAIN_MINUS:
      DBG("gain - (%d)\n", v4l2GetControl (cams[id].videoIn, V4L2_CID_GAIN));
      res = v4l2DownControl(cams[id].videoIn, V4L2_CID_GAIN);
      break;

    case IN_CMD_FOCUS_PLUS:
      DBG("focus + (%d)\n", focus);

      value=MIN(MAX(focus+10,0),255);

      if ( (res = v4l2SetControl(cams[id].videoIn, V4L2_CID_FOCUS, value, id, pglobal)) == 0) {
        focus = value;
      }
      res = focus;
      break;

    case IN_CMD_FOCUS_MINUS:
      DBG("focus - (%d)\n", focus);

      value=MIN(MAX(focus-10,0),255);

      if ( (res = v4l2SetControl(cams[id].videoIn, V4L2_CID_FOCUS, value, id, pglobal)) == 0) {
        focus = value;
      }
      res = focus;
      break;

    case IN_CMD_FOCUS_SET:
      value=MIN(MAX(value,0),255);
      DBG("set focus to %d\n", value);

      if ( (res = v4l2SetControl(cams[id].videoIn, V4L2_CID_FOCUS, value, id, pglobal)) == 0) {
        focus = value;
      }
      res = focus;
      break;

    /* switch the webcam LED permanently on */
    case IN_CMD_LED_ON:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_LED1_MODE, 1, id, pglobal);
    break;

    /* switch the webcam LED permanently off */
    case IN_CMD_LED_OFF:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_LED1_MODE, 0, id, pglobal);
    break;

    /* switch the webcam LED on if streaming, off if not streaming */
    case IN_CMD_LED_AUTO:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_LED1_MODE, 3, id, pglobal);
    break;

    /* let the webcam LED blink at a given hardcoded intervall */
    case IN_CMD_LED_BLINK:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_LED1_MODE, 2, id, pglobal);
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_LED1_FREQUENCY, 255, id, pglobal);
    break;

    case IN_CMD_EXPOSURE_MANUAL:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_EXPOSURE_AUTO, 0, id, pglobal);
      /*{ struct v4l2_control control;
      control.id    =V4L2_CID_EXPOSURE_AUTO_PRIORITY;
                    control.value =0;
                    if ((value = ioctl(videoIn->fd, VIDIOC_S_CTRL, &control)) < 0)
                        printf("Set Auto Exposure off error\n");
                    else
                        printf("11Auto Exposure set to %d\n", control.value);

    }*/
    printf("uga manual: %d\n", res);
    break;

    case IN_CMD_EXPOSURE_AUTO:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_EXPOSURE_AUTO, 3, id, pglobal);
      printf("uga auto: %d\n", res);
      /*  { struct v4l2_control control;
          control.id    =V4L2_CID_EXPOSURE_AUTO;
                        control.value =1;
                        if ((value = ioctl(videoIn->fd, VIDIOC_S_CTRL, &control)) < 0)
                            printf("Set Auto Exposure on error\n");
                        else
                            printf("22Auto Exposure set to %d\n", control.value);

      }*/
    break;

    case IN_CMD_EXPOSURE_SHUTTER_PRIO:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_EXPOSURE_AUTO, 4, id, pglobal);
    break;

    case IN_CMD_EXPOSURE_APERTURE_PRIO:
      res = v4l2SetControl(cams[id].videoIn, V4L2_CID_EXPOSURE_AUTO, 8, id, pglobal);
    break;

    default:
      DBG("nothing matched\n");
      res = -1;
  }

  if ( cmd != IN_CMD_RESET_PAN_TILT_NO_MUTEX )
    pthread_mutex_unlock( &cams[id].controls_mutex );

  return res;
}

/*
SRC: https://lists.berlios.de/pipermail/linux-uvc-devel/2007-July/001888.html

- dev: the device file descriptor
- pan: pan angle in 1/64th of degree
- tilt: tilt angle in 1/64th of degree
- reset: set to 1 to reset pan/tilt to the device origin, set to 0 otherwise
*/
int uvcPanTilt(int dev, int pan, int tilt, int reset) {
  struct v4l2_ext_control xctrls[2];
  struct v4l2_ext_controls ctrls;

  if (reset) {
    xctrls[0].id = V4L2_CID_PAN_RESET;
    xctrls[0].value = 3;

    ctrls.count = 1;
    ctrls.controls = xctrls;

    if ( ioctl(dev, VIDIOC_S_EXT_CTRLS, &ctrls) < 0 ) {
      return -1;
    }
    xctrls[0].id = V4L2_CID_TILT_RESET;
    if ( ioctl(dev, VIDIOC_S_EXT_CTRLS, &ctrls) < 0 ) {
      return -1;
    }
    return 0;
  } else {
    xctrls[0].id = V4L2_CID_PAN_RELATIVE;
    xctrls[0].value = pan;
    xctrls[1].id = V4L2_CID_TILT_RELATIVE;
    xctrls[1].value = tilt;

    ctrls.count = 2;
    ctrls.controls = xctrls;
  }

  if ( ioctl(dev, VIDIOC_S_EXT_CTRLS, &ctrls) < 0 ) {
    return -1;
  }

  return 0;
}
